﻿#include "ProjectAssetSubsystem.h"

#include "Asset/AssetSubsystem.h"
#include "Asset/AssetObject.h"
#include "Widgets/EditorProgressBarModalWindow.h"
#include "Framework/Editor.h"
#include "Project/ProjectModel.h"
#include "File/FileUtils.h"
#include "Output/Console.h"
#include "Async/PackagedTask.h"
#include "Object/ObjectPathUtils.h"
#include "EditorDefaultPackage.h"
#include "Widgets/Windows/MessageBox.h"

void RProjectAssetSubsystem::Initialize()
{
    ProjectAssetPackage=RProjectModel::Get()->GetAssetPackage().GetValue();

    EmptyAssetThumbnail=REditorDefaultPackage::Get()->GetIcon(U"MaximizeIcon");

    //搜索所有资产文件，读取资产头
    SPath ProjectDirectory=RProjectModel::Get()->GetProjectDirectory().GetValue();          

    //递归搜索所有资产文件
    for(auto &STDPath:std::filesystem::recursive_directory_iterator(
                ProjectDirectory.GetLowLevel()))
    {
        SPath Path=STDPath.path();
        if(!Path.IsAssetFile())
        {
            continue;
        }

        auto OptionalAssetInfo=CreateAssetInfo(Path);
        if(!OptionalAssetInfo.HasValue())
        {
            //这个资产是空的
            continue;
        }
        
        RegisterAssetInfo(Path,OptionalAssetInfo.GetValue());
    }

}

void RProjectAssetSubsystem::RenameAssetWithDialog(
    const SPath &OldAssetFilePath, const SPath &NewAssetFilePath)
{
    //1.加载资产
    auto OldThisAssetInfo=DetectedAssetInfos[OldAssetFilePath];
    SAssetPath OldAssetPath=OldThisAssetInfo.ReferencePath;
    auto OptionalLoadResult=RAssetSubsystem::Get()->LoadAsset(
        OldAssetPath
        ,EAssetSection::AllObjects
    );

    assert(OptionalLoadResult.HasValue());
    auto LoadResult=OptionalLoadResult.GetValue();
    
    //2.在改变资产路径之前删除旧资产，避免删除失败之后还得恢复对象资产路径
    auto Result=SFileUtils::DeleteFile(OldAssetFilePath);
    if(!Result.IsEmpty())
    {
        //删除旧资产失败
        WH::PrintLine(Result);
        return;
    }

    //3.对加载出来的资产对象重设资产路径和名称
    //以免这个对象在内存中的路径和磁盘上的路径不一致(对象有可能在重名之前就被加载到内存中)
    TSharedObjectPtr<RAssetObject> AssetObject=LoadResult.MainObject->GetOwner();
    CString OldObjectPath=LoadResult.MainObject->GetPath();
    SAssetPath NewAssetPath=OldAssetPath;
    CString AssetName= SAssetPath::AbstractAssetName(
            NewAssetFilePath
        );
    {
        NewAssetPath.SetFolderPathInPackage(
            SAssetPath::AbstractFolderPathInPackage(
                ProjectAssetPackage.Path
                ,NewAssetFilePath
            )
        );
        NewAssetPath.SetAssetName(
            AssetName
        );
    }
    AssetObject->SetAssetPath(NewAssetPath);
    LoadResult.MainObject->SetRawName(AssetName);
    CString NewObjectPath=LoadResult.MainObject->GetPath();

    //4.保存新资产
    auto NewAssetHead=RAssetSubsystem::Get()->ResaveLoadedAsset(
        LoadResult.MainObject
        ,LoadResult.EditInfoObject
        ,LoadResult.Thumbnail
    );

    //5.通知引用了这个资产的资产进行更新，资产可能很多在这一步需要进度条显示
    //遍历所有资产，找到引用了这个资产的资产，然后更新引用
    TVector<SPath> AllAssetFiles;
    TVector<SAssetHead> AllAssetHeads;
    for(auto& Pair: DetectedAssetInfos)
    {
        if(Pair.Value.AssetHead.Dependencies.Contains(OldAssetPath))
        {
            //这个资产引用了旧资产
            AllAssetFiles.Add(Pair.Key);
            AllAssetHeads.Add(Pair.Value.AssetHead);
        }
    }


    if(!AllAssetFiles.Empty())
    {

        //更新引用
        CString OldAssetPathString=OldAssetPath.ToString();
        CString NewAssetPathString=NewAssetPath.ToString();
        RProjectAssetSubsystemWeakPtr WeakThis=this;

        //使用子线程任务来更新引用,因为子线程可以在模态窗口中运行
        const int64_t TaskCount=AllAssetFiles.Num();
        int64_t TaskFinishedCount=0;
        TSharedObjectPtr<RWindow> ModalWindow;
        for(int64_t i=0;i<AllAssetFiles.Num();++i)
        {
            auto Task=WH::CreateAsyncTask<void>(
                [
                    &OldAssetPath
                    ,&NewAssetPath
                    ,&OldAssetPathString
                    ,&NewAssetPathString
                    ,&OldObjectPath
                    ,&NewObjectPath
                    ,&AllAssetFiles
                    ,&AllAssetHeads
                    ,i
                    ,&TaskFinishedCount
                    ,TaskCount
                    ,&ModalWindow
                    ,WeakThis
                ](bool& )
                {
                    SPath Path=AllAssetFiles[i];
                    SAssetHead AssetHead=AllAssetHeads[i];
                    auto OldAssetHeadSize=AssetHead.HeadSize;

                    auto IndexInDependencies=AssetHead.Dependencies.Find(OldAssetPath).GetValue();
                    AssetHead.Dependencies[IndexInDependencies]=NewAssetPath;

                    for(auto& External:AssetHead.ExternalObjects)
                    {
                        if(External.StartsWith(OldObjectPath))
                        {
                            External=External.Replace(OldObjectPath,NewObjectPath);
                        }
                    }

                    //读取文件
                    auto AssetFileData=SFileUtils::ReadToBinary(Path);

                    //替换文件头
                    AssetHead.HeadSize=RAssetSubsystem::ReplaceHead(AssetFileData,OldAssetHeadSize,AssetHead);
                    //写入文件
                    SFileUtils::SaveBinaryTo(AssetFileData,Path);       

                    //启动一个主线程任务来更新注册的资产信息
                    //以及关闭模态窗口
                    {
                        //所有任务完成
                        //启动一个主线程任务来关闭模态窗口
                        auto MainThreadTask=WH::CreateMainThreadTask<void>(
                            [
                                &ModalWindow
                                ,&TaskFinishedCount
                                ,TaskCount
                                ,Path
                                ,AssetHead
                                ,NewAssetPath
                                ,WeakThis
                            ](bool& )
                            {
                                //更新资产信息
                                if(WeakThis)
                                {
                                    auto OldAssetInfo=WeakThis->DetectedAssetInfos[Path];
                                    WeakThis->UnregisterAssetInfo(Path);
                                    SProjectAssetInfo NewAssetInfo=OldAssetInfo;
                                    NewAssetInfo.AssetHead=AssetHead;
                                    NewAssetInfo.ReferencePath=NewAssetPath;
                                    WeakThis->RegisterAssetInfo(Path,NewAssetInfo);
                                }

                                //关闭模态窗口
                                TaskFinishedCount++;
                                if(TaskFinishedCount==TaskCount && ModalWindow)
                                {
                                    WHE::CloseProgressBarModalWindow(ModalWindow);
                                }
                            }
                        );

                        MainThreadTask->Start();
                    }
                }
            );

            Task->Start();
        }



        //显示进度条
        WHE::SProgressBarModalWindowCreateParameter Parameter;
        Parameter.Percentage=[&TaskFinishedCount,TaskCount]()->float
        {
            return (float)TaskFinishedCount/TaskCount;
        };
        Parameter.PercentageText=U"正在更新引用...";

        //TSharedObjectPtr<RWindow> ModalWindow;
        WHE::ShowProgressBarModalWindow(Parameter,ModalWindow);

    }

    //6.更新资产信息
    UnregisterAssetInfo(OldAssetFilePath);
    RegisterAssetInfo(NewAssetFilePath
        ,CreateAssetInfo(NewAssetFilePath,NewAssetHead));

}

void RProjectAssetSubsystem::DeleteAssetWithDialog(const SPath &AssetFilePath)
{
    //1.找到该资产路径
    if(!DetectedAssetInfos.Contains(AssetFilePath))
    {
        //这个资产是空的,直接删除
        SFileUtils::DeleteFile(AssetFilePath);
        return;
    }

    auto ThisAssetInfo=DetectedAssetInfos[AssetFilePath];
    SAssetPath AssetPath=ThisAssetInfo.ReferencePath;

    //2查找引用了这个资产的资产，如果有就停止删除
    for(auto& Pair: DetectedAssetInfos)
    {
        if(Pair.Value.ReferencePath==AssetPath)
        {
            //跳过本身
            continue;
        }

        if(Pair.Value.AssetHead.Dependencies.Contains(AssetPath))
        {
            //找到引用了这个资产的资产
            //显示一个对话框，提示用户删除失败
            //因为这个资产被引用了
            CString Error=U"无法删除资产文件"+AssetFilePath.ToString()
                +U"，因为另一个资产文件"+Pair.Key.ToString()
                +U"引用了该资产";

            WH::ShowMessageBox(U"无法删除",
                Error);
            return;
        }
        
    }

    //4.删除资产信息
    UnregisterAssetInfo(AssetFilePath);

    //5.删除资产文件
    auto Result=SFileUtils::DeleteFile(AssetFilePath);
    if(!Result.IsEmpty())
    {
        //删除资产失败
        WH::PrintLine(Result);
        return;
    }
}

TOptional<SProjectAssetInfo> RProjectAssetSubsystem::DetectAssetInfo(const SPath &AssetFilePath)
{
    if(DetectedAssetInfos.Contains(AssetFilePath))
    {
        return DetectedAssetInfos[AssetFilePath];
    }

    auto OptionalAssetInfo=CreateAssetInfo(AssetFilePath);
    if(!OptionalAssetInfo)
    {
        //这个资产是空的
        return SProjectAssetInfo();
    }
    RegisterAssetInfo(AssetFilePath,OptionalAssetInfo.GetValue());

    return OptionalAssetInfo.GetValue();
}

void RProjectAssetSubsystem::RegisterAssetInfo(const SPath &AssetFilePath, const SProjectAssetInfo &AssetInfo)
{
    DetectedAssetInfos.Add(AssetFilePath,AssetInfo);
}

void RProjectAssetSubsystem::UnregisterAssetInfo(const SPath &AssetFilePath)
{
    DetectedAssetInfos.Remove(AssetFilePath);
}

TVector<SProjectAssetInfo> RProjectAssetSubsystem::GenerateAssetListByClass(CClass *InClass)
{
    TVector<SProjectAssetInfo> Result;
    Result.Reserve(1000);
    for(auto& Pair:DetectedAssetInfos)
    {
        if(Pair.Value.AssetHead.MainObjectClass->CanObjectConvertTo(InClass))
        {
            Result.Add(Pair.Value);
        }
    }

    return Result;
}

void RProjectAssetSubsystem::AsyncLoadThumbnail(const SPath &AssetFilePath, TDelegate<void, TSharedObjectPtr<RTexture2D>> Callback)
{
    if(!DetectedAssetInfos.Contains(AssetFilePath))
    {
        //该资产文件没有注册，应当是一个空的资产文件
        //使用空资产文件的缩略图
        Callback.ExecuteIfBound(EmptyAssetThumbnail);
        return;
    }

    auto AssetInfo=DetectedAssetInfos[AssetFilePath];
    if(AssetInfo.Thumbnail.IsValid())
    {
        Callback.ExecuteIfBound(AssetInfo.Thumbnail);
        return;
    }

    //异步加载缩略图
    //TODO 确保在异步加载这个资产的时候，资产文件没有被主线程删除
    RAssetSubsystem::Get()->AsyncLoadAsset(
        AssetInfo.ReferencePath
        ,EAssetSection::Thumbnail
        ,[this,AssetFilePath,Callback](const SAssetLoadResult& LoadResult)
        {
            if(!LoadResult.Thumbnail)
            {
                //没有找到缩略图
                return;
            }

            if(!DetectedAssetInfos.Contains(AssetFilePath))
            {
                //这个资产已经被删除了
                return;
            }

            auto& AssetInfo=DetectedAssetInfos[AssetFilePath];
            AssetInfo.Thumbnail=LoadResult.Thumbnail;
            Callback.ExecuteIfBound(AssetInfo.Thumbnail);
        }
    );

}

TOptional<SProjectAssetInfo> RProjectAssetSubsystem::CreateAssetInfo(const SPath &AssetFilePath)
{
    auto OptionalAssetHead=RAssetSubsystem::Get()->LoadAssetHead(AssetFilePath);
    if(!OptionalAssetHead)
    {
        //这个资产头是空的
        return TOptional<SProjectAssetInfo>();
    }

    return CreateAssetInfo(
        AssetFilePath
        ,OptionalAssetHead.GetValue());
}

SProjectAssetInfo RProjectAssetSubsystem::CreateAssetInfo(const SPath &AssetFilePath, const SAssetHead &InAssetHead)
{
    SProjectAssetInfo AssetInfo;
    AssetInfo.AssetHead=InAssetHead;
    AssetInfo.Path=AssetFilePath;
    AssetInfo.ReferencePath= 
        RAssetSubsystem::Get()->CreateAssetPath(
            AssetInfo.AssetHead.MainObjectClass
            ,ProjectAssetPackage.Name
            ,AssetFilePath
        );
    return AssetInfo;
}
